

/* 
 * class Parser
 * 
 * Parses the tokens returned from the lexical analyzer.
 * 
 * Update the parser implementation to parse the Decaf
 * language according to the grammar provided. 
 */
public class Parser {

	private Lexer lexer; // lexical analyzer
	private Token token; // look ahead token

	public Parser(Lexer lexer2) {
		lexer = lexer2;
	}

	public ClassDecl parse() throws SyntacticException {
		token = lexer.nextToken();

		if (!token.getLexeme().equals("class"))
			throw new SyntacticException();
		match(Token.KW);
		String className = token.getLexeme();
		match(Token.ID);
		match(Token.LB);
		ClassDecl value = new ClassDecl(className, methodDecls());
		match(Token.RB);
		if (token.getTokenType() == Token.EOF)
			return value;
		throw new SyntacticException();
	}

	private MethodsDecl methodDecls() throws SyntacticException {
		MethodsDecl value = new MethodsDecl();
		if (token.getTokenType() == Token.RB)
			return new MethodsDecl();
		while (token.getTokenType() != Token.RB)
			value.addMethodDecl(methodDecl());
		return value;
	}

	private MethodDecl methodDecl() throws SyntacticException {
		if (!token.getLexeme().equals("static"))
			throw new SyntacticException();
		match(Token.KW);
		Type type = type();
		String methodName = token.getLexeme();
		match(Token.ID);
		match(Token.LP);
		FormalParams formalParams = formalParams();
		match(Token.RP);
		Block block = block();
		MethodDecl value = new MethodDecl(methodName, type, formalParams, block);
		return value;
	}

	private FormalParams formalParams() throws SyntacticException {
		if (token.getTokenType() == Token.RP)
			return null;
		FormalParams value = properFormalParams();
		return value;
	}

	private ProperFormalParams properFormalParams() throws SyntacticException {
		ProperFormalParams value = new ProperFormalParams(formalParam());
		while (true) {
			switch (token.getTokenType()) {
			case Token.FA:
				match(token.getTokenType());
				value.addParam(formalParam());
				break;
			default:
				return value;
			}
		}
	}

	private FormalParam formalParam() throws SyntacticException {
		Type type = type();
		String id = token.getLexeme();
		match(Token.ID);
		FormalParam value = new FormalParam(id, type);
		return value;
	}

	private Type type() throws SyntacticException {
		String[] kw = { "int", "boolean", "float", "String" };
		int[] typesInt = { Token.NM, Token.BL, Token.NM, Token.ST };
		int index = -1;
		boolean bvalue = false;
		for (int i = 0; i < kw.length; i++) {
			if (kw[i].equals(token.getLexeme())) {
				index = i;
				bvalue = true;
				break;
			}
		}
		if (!bvalue)
			throw new SyntacticException();
		Type value = new Type(typesInt[index]);
		match(Token.KW);
		return value;
	}

	private Block block() throws SyntacticException {
		match(Token.LB);
		Block value = new Block(statements());
		match(Token.RB);
		return value;
	}

	private Statements statements() throws SyntacticException {
		// epsilon
		Statements value = new Statements();
		if (token.getTokenType() == Token.RB)
			return new Statements();
		while (token.getTokenType() != Token.RB)
			value.addstatement(statement());
		return value;
	}

	private Statement statement() throws SyntacticException {
		// look ahead
		switch (token.getTokenType()) {
		case Token.LB:
			return block();
		case Token.ID:
			return assignStmt();
		case Token.KW:
			if (token.getLexeme().equals("if"))
				return ifStmt();
			else if (token.getLexeme().equals("while"))
				return whileStmt();
			else if (token.getLexeme().equals("return"))
				return returnStmt();
			else
				return localVarDecl();
		default:
			return localVarDecl();
		}
	}

	private LocalVarDecl localVarDecl() throws SyntacticException {
		Type type = type();
		String id = token.getLexeme();
		match(Token.ID);
		match(Token.SM);
		LocalVarDecl value = new LocalVarDecl(id, type);
		return value;
	}

	private AssignStmt assignStmt() throws SyntacticException {
		String id = token.getLexeme();
		match(Token.ID);
		match(Token.AO);
		Expression expr = expression();
		AssignStmt value = new AssignStmt(id, expr);
		match(Token.SM);
		return value;
	}

	private IfStmt ifStmt() throws SyntacticException {
		if (!token.getLexeme().equals("if"))
			throw new SyntacticException();
		match(Token.KW);
		match(Token.LP);
		Expression expression = expression();
		match(Token.RP);
		Statement statement1 = statement();
		IfStmt value = new IfStmt(expression, statement1);
		if (token.getTokenType() == Token.KW
				&& token.getLexeme().equals("else")) {
			match(token.getTokenType());
			value.setElseStatement(statement());
			return value;
		}
		return value;
	}

	private WhileStmt whileStmt() throws SyntacticException {
		if (!token.getLexeme().equals("while"))
			throw new SyntacticException();
		match(Token.KW);
		match(Token.LP);
		Expression expression = expression();
		match(Token.RP);
		Statement statement = statement();
		WhileStmt value = new WhileStmt(expression, statement);
		return value;
	}

	private ReturnStmt returnStmt() throws SyntacticException {
		if (!token.getLexeme().equals("return"))
			throw new SyntacticException();
		match(Token.KW);
		ReturnStmt value = new ReturnStmt(expression());
		match(Token.SM);
		return value;
	}

	private Expression expression() throws SyntacticException {
		Expression value = conditionalAndExpr();
		while (true) {
			switch (token.getTokenType()) {
			case Token.LO:
				match(token.getTokenType());
				value = new Expression(conditionalAndExpr(), value);
				break;
			default:
				return value;
			}
		}
	}

	private ConditionalAndExpr conditionalAndExpr() throws SyntacticException {
		ConditionalAndExpr value = equalityExpr();
		while (true) {
			switch (token.getTokenType()) {
			case Token.LA:
				match(token.getTokenType());
				value = new ConditionalAndExpr(equalityExpr(), value);
				break;
			default:
				return value;
			}
		}
	}

	private EqualityExpr equalityExpr() throws SyntacticException {
		EqualityExpr value = additiveExpr();
		while (true) {
			switch (token.getTokenType()) {
			case Token.EQ:
			case Token.NE:
				int type = token.getTokenType();
				match(token.getTokenType());
				value = new EqualityExpr(additiveExpr(), value, type);
				break;
			default:
				return value;
			}
		}
	}

	private AdditiveExpr additiveExpr() throws SyntacticException {
		AdditiveExpr value = multiplicativeExpr();
		while (true) {
			switch (token.getTokenType()) {
			case Token.PO:
			case Token.MO:
				int type = token.getTokenType();
				match(token.getTokenType());
				value = new AdditiveExpr(multiplicativeExpr(), value, type);

				break;
			default:
				return value;
			}
		}
	}

	private MultiplicativeExpr multiplicativeExpr() throws SyntacticException {
		MultiplicativeExpr value = primaryExpr();
		while (true) {
			switch (token.getTokenType()) {
			case Token.TO:
			case Token.DO:
			case Token.MD:
				int type = token.getTokenType();
				match(token.getTokenType());
				value = new MultiplicativeExpr(primaryExpr(), value, type);

				break;

			default:
				return value;
			}
		}
	}

	private PrimaryExpr primaryExpr() throws SyntacticException {
		PrimaryExpr value = new PrimaryExpr();
		switch (token.getTokenType()) {
		case Token.NM:
		case Token.BL:
		case Token.ST:
			value.setFlag(PrimaryExpr.constant_t);
			value.setType(token.getTokenType());
			value.setLexeme(token.getLexeme());
			match(token.getTokenType());
			return value;
		case Token.LP: {
			match(Token.LP);
			value = new PrimaryExpr(expression());
			value.setFlag(PrimaryExpr.expression_t);
			match(Token.RP);
			return value;
		}
		default:
			break;
		}
		if (token.getTokenType() == Token.LP) {
			match(Token.LP);
			value = new PrimaryExpr(expression());
			value.setFlag(PrimaryExpr.expression_t);
			match(Token.RP);
			return value;
		}
		String id = token.getLexeme();
		match(Token.ID);
		// Left factor id and callexpr
		// if next is left bracket, then match callexpr
		if (token.getTokenType() == Token.LP) {
			match(Token.LP);
			ActualParams actual = actualParams();
			match(Token.RP);
			value = new CallExpr(id, actual);
			return value;
		} else {
			// ID matched
			value = new PrimaryExpr(id);
			value.setFlag(PrimaryExpr.constant_t);
			value.setType(Token.ID);
			return value;
		}
	}

	private ActualParams actualParams() throws SyntacticException {
		// epislon matched, as it comes only in callexpr
		if (token.getTokenType() == Token.RP) {
			System.out.println("NULL");
			return null;
		}
		return properActualParams();
	}

	private ProperActualParams properActualParams() throws SyntacticException {
		ProperActualParams value = new ProperActualParams();
		value.addExpr(expression());
		while (true) {
			switch (token.getTokenType()) {
			case Token.FA:
				match(token.getTokenType());
				value.addExpr(expression());
				break;
			default:
				return value;
			}
		}
	}

	/*
	 * private boolean expr() throws SyntacticException { boolean value =
	 * term();
	 * 
	 * while (true) { switch (token.getTokenType()) { case Token.MO: value &=
	 * match(token.getTokenType()); value &= term(); break;
	 * 
	 * case Token.PO: value &= match(token.getTokenType()); value &= term();
	 * break;
	 * 
	 * default: return value; } } }
	 * 
	 * private boolean term() throws SyntacticException { boolean value =
	 * match(Token.NM);
	 * 
	 * while (true) { switch (token.getTokenType()) {
	 * 
	 * case Token.DO: value &= match(token.getTokenType()); value &=
	 * match(Token.NM); break;
	 * 
	 * case Token.TO: value &= match(token.getTokenType()); value &=
	 * match(Token.NM); break;
	 * 
	 * default: return value; } } }
	 */

	private void match(int t) throws SyntacticException {
		if (token.getTokenType() == t) {
			token = lexer.nextToken();
			return;
		}
		throw new SyntacticException("Didn't match token :" + token.getLexeme()
				+ " with type " + t);
	}
}
